/*
 * Copyright (C) 2010 Fred Barrie This program is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version. This program is distributed
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received
 * a copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package org.aergia.vinny.database.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.aergia.vinny.database.Database;
import org.aergia.vinny.database.pojo.DatabasePojo;
import org.aergia.vinny.util.DateUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * @author Fred
 */
public final class BackupUtil implements Runnable {

	private static final String EXTENSION = "backup";

	public static void backupDatabase(final Database database) {
		final Logger logger = Logger.getLogger(BackupUtil.class.getCanonicalName());
		final BackupUtil backup = new BackupUtil(database, logger);
		Executors.newSingleThreadExecutor().execute(backup);
	}

	private final Database database;

	private final Logger logger;

	private BackupUtil(final Database database, final Logger logger) {
		this.database = database;
		this.logger = logger;
	}

	@Override
	public void run() {
		this.logger.info("Database backup started.");
		final String todaysDate = DateUtil.formatBackupDate();
		final String backupFileName = System.getProperty("user.home") + File.separator + "vinny" + File.separator + "backups" + File.separator + "vinny-"
				+ todaysDate + "." + BackupUtil.EXTENSION;
		this.logger.info("Backup database for today's date: " + todaysDate);

		final File backupFile = new File(backupFileName);

		if (backupFile.exists()) {
			this.logger.info("Backup file for today's date already exists.");
			return;
		}

		backupFile.getParentFile().mkdirs();

		this.logger.info("Database export file name: " + backupFile.getAbsolutePath());

		try {

			final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			final Document document = builder.newDocument();

			final Element root = document.createElement("Database");
			document.appendChild(root);

			this.addPojoClass(document, root, "Organization-List", this.database.getOrganizations());
			this.addPojoClass(document, root, "Location-List", this.database.getLocations());
			this.addPojoClass(document, root, "Operating-Hours", this.database.getOperatingHours());
			this.addPojoClass(document, root, "Volunteer-List", this.database.getVolunteers());
			this.addPojoClass(document, root, "Card-List", this.database.getCards());
			this.addPojoClass(document, root, "Scan-List", this.database.getScans());
			this.addPojoClass(document, root, "Service-List", this.database.getServices());
			this.addPojoClass(document, root, "Group-List", this.database.getGroups());
			this.addPojoClass(document, root, "Option-List", this.database.getOptions());
			this.addPojoClass(document, root, "Skill-List", this.database.getSkills());
			this.addPojoClass(document, root, "SkillRate-List", this.database.getSkillRates());
			this.addPojoClass(document, root, "Role-List", this.database.getRoles());
			this.addPojoClass(document, root, "User-List", this.database.getUsers());
			this.addPojoClass(document, root, "Workstation-List", this.database.getWorkstations());

			final Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty(OutputKeys.METHOD, "xml");

			final ByteArrayOutputStream output = new ByteArrayOutputStream();
			final StreamResult result = new StreamResult(output);
			transformer.transform(new DOMSource(document), result);
			output.flush();
			output.close();

			this.compressBackupFile(backupFile, output.toByteArray());

			this.logger.info("Database backup finished.");

		} catch (final IOException e) {
			this.logger.warning("Database backup failed. " + e.getMessage());

		} catch (final Throwable t) {
			this.logger.warning("Database backup failed. " + t.getMessage());
		}
	}

	private void addPojoClass(final Document document, final Element root, final String elementName, final Collection<?> objects) throws Exception {
		this.logger.fine("Backing up " + elementName + " table.");
		final Element classElement = document.createElement(elementName);
		root.appendChild(classElement);
		for (final Object object : objects) {
			if (object instanceof DatabasePojo) {
				final DatabasePojo pojo = (DatabasePojo) object;
				final Element element = document.createElement(pojo.getClass().getSimpleName());
				try {
					pojo.attachAttributes(element);
					classElement.appendChild(element);
				} catch (final Throwable t) {
					this.logger.log(Level.WARNING, "Error backing up " + elementName + " (" + pojo.getId() + ")", t);
					throw new Exception(t);
				}
			}
		}
	}

	private void compressBackupFile(final File backupFile, final byte[] buffer) throws IOException {

		final CRC32 crc = new CRC32();
		final ZipOutputStream output = new ZipOutputStream(new FileOutputStream(backupFile));
		output.setLevel(9);

		final ZipEntry entry = new ZipEntry(backupFile.getName().substring(0, backupFile.getName().length() - BackupUtil.EXTENSION.length()) + "xml");
		entry.setSize(buffer.length);
		crc.reset();
		crc.update(buffer);
		entry.setCrc(crc.getValue());
		output.putNextEntry(entry);
		output.write(buffer, 0, buffer.length);
		output.finish();
		output.close();
	}
}
